//using UnityEngine;
using System;
using System.Collections;
using System.Threading;
using System.Collections.Generic;

namespace MxNetwork
{
    public class MxNetworkManager
    {
        [ThreadStatic]
        static AutoResetEvent LocalEvent = new AutoResetEvent(false);
                
        private readonly static  MxNetworkManager m_instance = new MxNetworkManager();
        private System.Collections.ArrayList m_Connecters;
        private System.Collections.ArrayList m_Listeners;

        private MxNetworkManager()
        {
            m_Connecters = new System.Collections.ArrayList();
            m_Listeners = new System.Collections.ArrayList();
            ThreadPool.SetMaxThreads(5, 2);
        }

        public static MxNetworkManager GetInstance()
        {
            return m_instance;
        }

        public MxNwkListener CreateListener(MxNwkListenerDesc Desc, MxNwkObsListener ObsListener, MxNwkObsChannel ObsChannel)
        {
            if((Desc == null) || (ObsListener == null) || (ObsChannel == null))
            {
                return null;
            }
            
            MxNwkListener Listener = new MxNwkListener();
            if(Listener.Create(Desc, ObsListener, ObsChannel))
            {
                ObsListener.Handle = Listener;
                lock(m_Listeners.SyncRoot)
                {
                    m_Listeners.Add(Listener);
                }
                return Listener;
            }
            else
            {
                return null;
            }            
        }

        public MxNwkConnecter CreateConnecter(MxNwkConnecterDesc Desc, MxNwkObsConnecter ObsConnecter, MxNwkObsChannel ObsChannel, bool IsReconnect)
        {
            if ((Desc == null) || (ObsConnecter == null) || (ObsChannel == null))
            {
                return null;
            }

            MxNwkConnecter Connecter = new MxNwkConnecter();
            if (Connecter.Create(Desc, ObsConnecter, ObsChannel, IsReconnect))
            {
                ObsConnecter.Handle = Connecter;
                lock(m_Connecters.SyncRoot)
                {
                    m_Connecters.Add(Connecter);
                }
                return Connecter;
            }
            else
            {
                return null;
            }            
        }

        public bool CloseChannel(int hChannel)
        {
            MxNwkChannel tmp = null;
            m_ChannelsLock.EnterWriteLock();
            try
            {               
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    m_channels.Remove(hChannel);
                }                
            }
            finally
            {
                m_ChannelsLock.ExitWriteLock();
            }           

            if(tmp != null)
            {
                tmp.Dispose();
                return true;
            }
            else
            {
                return false;
            }
        }

	    bool GetChannelDesc(int hChannel, ref MxNwkChannelDesc desc)
        {
            m_ChannelsLock.EnterReadLock();            
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    desc = tmp.GetChannelDesc();
                    return true;
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                m_ChannelsLock.ExitReadLock();                
            }           
        }

        public bool SendMsg(int hChannel, MxNwkPacket pMsg)
        {
            m_ChannelsLock.EnterReadLock();
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    return tmp.SendMsg(pMsg);
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                m_ChannelsLock.ExitReadLock();
            }           
        }

	    public bool SendRequest(int hChannel, MxNwkPacket pRequest, MxToken pAct, int overtimeSec)
        {
            m_ChannelsLock.EnterReadLock();
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    return tmp.SendRequest(pRequest, pAct, overtimeSec);
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                m_ChannelsLock.ExitReadLock();
            }           
        }

        public bool SendRequest(int hChannel, MxNwkPacket pRequest)
        {
            return SendRequest(hChannel, pRequest, null, -1);
        }

        public bool SendRequest(int hChannel, MxNwkPacket pRequest, MxToken pAct)
        {
            return SendRequest(hChannel, pRequest, pAct, -1);
        }

        
        public bool SendSynRequest(int hChannel, MxNwkPacket pRequest, ref MxNwkPacket pAck)  
        {
            if (LocalEvent == null)
            {
                LocalEvent = new AutoResetEvent(false);
            }
            LocalEvent.Reset();
            pRequest.ReqEvent = LocalEvent;
            if (SendRequest(hChannel, pRequest) == false)
            {
                return false;
            }

            if(LocalEvent.WaitOne(Timeout.Infinite, false))
            {
                RemoveRecvAck(pRequest.GetPacketToken(), ref pAck);
                return true;
            }
            else
            {
                RemoveSendRequest(pRequest.GetPacketToken());
                pAck = new MxNwkPacket(MxNwkPacket.MxNwkPacketType.NPT_ACK);
                pAck.SetPacketResult(1);
                pAck.SetPacketID(pRequest.GetPacketID());
                pAck.SetPacketToken(pRequest.GetPacketToken());
                pAck.SetPacketChannelID(hChannel);               
                return false;
            }            
        }

        
        public bool SendSynRequest(int hChannel, MxNwkPacket pRequest, ref MxNwkPacket pAck, int SecondTimeOut)
        {
            if (LocalEvent == null)
            {
                LocalEvent = new AutoResetEvent(false);
            }
            LocalEvent.Reset();
            pRequest.ReqEvent = LocalEvent;
            if (SendRequest(hChannel, pRequest) == false)
            {
                return false;
            }

            if (LocalEvent.WaitOne(SecondTimeOut * 1000, false))
            {
                RemoveRecvAck(pRequest.GetPacketToken(), ref pAck);                
                return true;
            }
            else
            {
                RemoveSendRequest(pRequest.GetPacketToken());
                pAck = new MxNwkPacket(MxNwkPacket.MxNwkPacketType.NPT_ACK);
                pAck.SetPacketResult(1);
                pAck.SetPacketID(pRequest.GetPacketID());
                pAck.SetPacketToken(pRequest.GetPacketToken());
                pAck.SetPacketChannelID(hChannel);               
                return false;
            }
        }

	    public bool SendAck(int hChannel, MxNwkPacket pAck)
        {
            m_ChannelsLock.EnterReadLock();
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    return tmp.SendAck(pAck);
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                m_ChannelsLock.ExitReadLock();
            }           
        }

	    public bool SendAck(MxNwkPacket pRequest, MxNwkPacket pAck)
        {
            pAck.SetPacketID(pRequest.GetPacketID());
            pAck.SetPacketToken(pRequest.GetPacketToken());
            pAck.SetPacketUserID(pRequest.GetPacketUserID());
            pAck.SetPacketCharacterID(pRequest.GetPacketCharacterID());

            m_ChannelsLock.EnterReadLock();
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(pRequest.GetPacketChannelID(), out tmp))
                {
                    return tmp.SendAck(pAck);
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                m_ChannelsLock.ExitReadLock();
            }           
        }

        public bool RegMsg(uint id, MxMsgObserver obs)
        {
            m_MsgObservers[id] = obs;
            return true;
        }
        public bool RegDefaultMsg(MxMsgObserver obs)
        {
            m_defaultMsgObserver = obs;
            return true;
        }

        public bool ExistRegMsg(uint id)
        {
            MxMsgObserver tmp = null;
            if (m_MsgObservers.TryGetValue(id, out tmp))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool RegRequest(uint id, MxRequestObserver obs)
        {
            m_RequestObservers[id] = obs;
            return true;
        }

        public bool RegDefaultRequest(MxRequestObserver obs)
        {
            m_defaultRequestObserver = obs;
            return true;
        }

        public bool ExistRegRequest(uint id)
        {
            MxRequestObserver tmp = null;
            if (m_RequestObservers.TryGetValue(id, out tmp))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool RegAck(uint id, MxAckObserver obs)
        {
            m_AckObservers[id] = obs;
            return true;
        }

        public bool RegDefaultAck(MxAckObserver obs)
        {
            m_defaultAckObserver = obs;
            return true;
        }

        public bool ExistRegAck(uint id)
        {
            MxAckObserver tmp = null;
            if (m_AckObservers.TryGetValue(id, out tmp))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public void SetDefaultOvertimeSec(int sec)
        {
            if (sec < 0)
            {
                m_defaultOverTimeSec = 0;
            }
            else
            {
                m_defaultOverTimeSec = sec;
            }	
        }

        public int GetDefaultOvertimeSec()
        {
            return m_defaultOverTimeSec;
        }

        public bool AddChannel(MxNwkChannel channel)
        {
            m_ChannelsLock.EnterWriteLock();
            try
            {
                m_channels[channel.GetChannelDesc().hChannel] = channel;
                return true;
            }
            finally
            {
                m_ChannelsLock.ExitWriteLock();
            }           
        }

	    public MxNwkChannel RemoveChannel(int hChannel)
        {
            m_ChannelsLock.EnterWriteLock();
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    m_channels.Remove(hChannel);  
                }
                return tmp;
            }
            finally
            {
                m_ChannelsLock.ExitWriteLock();
            }           
        }

        public int AssignChannelHandle()
        {
             return Interlocked.Increment(ref m_ChannelID);
        }

        public int GenRequestTokenID()
        {
             return Interlocked.Increment(ref m_RequestTokenID);
        }

        public void AddTmpChannel(MxNwkChannel pChannel)
        {
            lock(m_LockForTmpChannels)
            {
                m_lstForTmpChannels.Add(pChannel);
            }           
        }

        public void RemoveTmpChannel(MxNwkChannel pChannel)
        {
             lock(m_LockForTmpChannels)
             {
                 for(int i=0; i<m_lstForTmpChannels.Count; ++i)
                 {
                     if(m_lstForTmpChannels[i].m_pDesc.hChannel == pChannel.m_pDesc.hChannel)
                     {
                         m_lstForTmpChannels.RemoveAt(i);
                         break;
                     }
                 }
             }
        }

        public void UpdateTmpChannels()
        {
            TimeSpan CurrentTime = new TimeSpan(DateTime.Now.Ticks);
           
            lock(m_LockForTmpChannels)
            {
                for(int i=0; i<m_lstForTmpChannels.Count;)
                {                    
                    if (CurrentTime.Seconds - m_lstForTmpChannels[i].GetChannelDesc().CreateTimeSec > 120)
                    {
                        m_lstForTmpChannels[i].Dispose();
                        m_lstForTmpChannels.RemoveAt(i);
                    }
                    else
                    {
                        ++i;
                    }
                }
            }
        }

        public void SubmitMsg(MxNwkPacket pMsg)
        {
            MxMsgObserver tmp = null;
            if (m_MsgObservers.TryGetValue(pMsg.GetPacketID(), out tmp))
            {
                tmp.OnSubmit(pMsg);
            }   
            else if(m_defaultMsgObserver != null)
            {
                m_defaultMsgObserver.OnSubmit(pMsg);
            }
        }

	    public void SubmitRequest(MxNwkPacket pRequest)
        {
            MxRequestObserver tmp = null;
            if (m_RequestObservers.TryGetValue(pRequest.GetPacketID(), out tmp))
            {
                tmp.OnSubmit(pRequest);
            }
            else if (m_defaultRequestObserver != null)
            {
                m_defaultRequestObserver.OnSubmit(pRequest);
            }
        }

	    public void SubmitAck(MxNwkPacket pAck)
        {
            MxNwkPacket pRequest = null;
            MxToken pAct = null;
            RemoveSendRequest(pAck.GetPacketToken(), ref pRequest, ref pAct);
            if(pRequest != null)
            {
                if(pRequest.ReqEvent != null)
                {
                    AddRecvAck(pAck);
                    pRequest.ReqEvent.Set();
                }
                else
                {
                    MxAckObserver tmp = null;
                    if (m_AckObservers.TryGetValue(pAck.GetPacketID(), out tmp))
                    {
                        tmp.OnSubmit(pRequest, pAck, pAct);
                    }
                    else if (m_defaultAckObserver != null)
                    {
                        m_defaultAckObserver.OnSubmit(pRequest, pAck, pAct);
                    }
                }                
            }            
        } 

        public void AddSendRequest(MxNwkPacket request, MxToken token)
        {
            lock (m_LockForSendRequests)
            {
                RequestAndToken tmp = new RequestAndToken();
                tmp.m_Request = request;
                tmp.m_Token = token;
                m_SendRequests[request.GetPacketToken()] = tmp;
            }    
        }

        public void RemoveSendRequest(int tokenID, ref MxNwkPacket request, ref MxToken token)
        {
            lock (m_LockForSendRequests)
            {
                RequestAndToken tmp = null;
                if (m_SendRequests.TryGetValue(tokenID, out tmp))
                {
                    request = tmp.m_Request;
                    token = tmp.m_Token;
                    m_SendRequests.Remove(tokenID);
                }                
            }
        }   

        public void RemoveSendRequest(int tokenID)
        {
            lock (m_LockForSendRequests)
            {
                m_channels.Remove(tokenID);
            }
        }

        public void AddRecvAck(MxNwkPacket Ack)
        {
            lock (m_LockForRecvAcks)
            {
                m_RecvAcks[Ack.GetPacketToken()] = Ack;
            }
        }

        public void RemoveRecvAck(int tokenID, ref MxNwkPacket Ack)
        {
            lock (m_LockForRecvAcks)
            {
                if (m_RecvAcks.TryGetValue(tokenID, out Ack))
                {                    
                    m_SendRequests.Remove(tokenID);
                }
            }
        }   

        public bool ExistSendRequest(int tokenID)
        {
            lock (m_LockForSendRequests)
            {
                RequestAndToken tmp = null;
                if (m_SendRequests.TryGetValue(tokenID, out tmp))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }

        public bool CallOnRecvPacket(int hChannel, MxNwkPacket pPkt)
        {   
            m_ChannelsLock.EnterReadLock();
            try
            {
                MxNwkChannel tmp = null;
                if (m_channels.TryGetValue(hChannel, out tmp))
                {
                    return tmp.CallOnRecvPacket(hChannel, pPkt);
                }
                else
                {
                    return false;
                }
            }
            finally
            {
                m_ChannelsLock.ExitReadLock();
            }           
        }

        public void DispatchJob(WaitCallback callBack, object state)
        {
            ThreadPool.QueueUserWorkItem(callBack, state);
        }

        private Dictionary<int, MxNwkChannel> m_channels = new Dictionary<int, MxNwkChannel>();
        private ReaderWriterLockSlim m_ChannelsLock = new ReaderWriterLockSlim();

        private List<MxNwkChannel> m_lstForTmpChannels = new List<MxNwkChannel>();
        private object m_LockForTmpChannels = new object();

        class RequestAndToken
        {
            public MxNwkPacket m_Request;
            public MxToken m_Token;
        }

        private Dictionary<int, RequestAndToken> m_SendRequests = new Dictionary<int, RequestAndToken>();
        private object m_LockForSendRequests = new object();

        private Dictionary<int, MxNwkPacket> m_RecvAcks = new Dictionary<int, MxNwkPacket>();
        private object m_LockForRecvAcks = new object();

        private /*volatile*/ int m_ChannelID = 0;
        private /*volatile*/ int m_RequestTokenID = 0;

        private MxMsgObserver m_defaultMsgObserver = null;
        private Dictionary<uint, MxMsgObserver> m_MsgObservers = new Dictionary<uint, MxMsgObserver>();

        private MxRequestObserver m_defaultRequestObserver = null;
        private Dictionary<uint, MxRequestObserver> m_RequestObservers = new Dictionary<uint, MxRequestObserver>();

        private MxAckObserver m_defaultAckObserver = null;
        private Dictionary<uint, MxAckObserver> m_AckObservers = new Dictionary<uint, MxAckObserver>();
        private int m_defaultOverTimeSec;
    }
}

